/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.formserver.viewmodel.core.buildformformat;

import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspElementDataType;
import com.inspur.edp.cef.designtime.api.variable.CommonVariable;
import com.inspur.edp.cef.designtime.api.variable.CommonVariableCollection;
import com.inspur.edp.formserver.viewmodel.GspViewModel;
import com.inspur.edp.formserver.viewmodel.exception.ViewModelException;
import com.inspur.edp.formserver.viewmodel.formentity.Button;
import com.inspur.edp.formserver.viewmodel.formentity.ButtonGroup;
import com.inspur.edp.formserver.viewmodel.formentity.ChildData;
import com.inspur.edp.formserver.viewmodel.formentity.MethodGroup;
import com.inspur.edp.formserver.viewmodel.formentity.MethodParam;
import com.inspur.edp.formserver.viewmodel.formentity.ObjectData;
import com.inspur.edp.formserver.viewmodel.formentity.Parameters;
import com.inspur.edp.formserver.viewmodel.formentity.VoFormModel;
import com.inspur.edp.formserver.viewmodel.i18n.VMI8nResourceUtil;
import com.inspur.edp.formserver.viewmodel.i18n.names.VoResourceKeyNames;
import com.inspur.edp.formserver.vmapi.formconfig.VoBuildFormFormatService;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.lcm.metadata.api.service.MetadataService;
import com.inspur.edp.lcm.metadata.api.service.NoCodeService;
import com.inspur.edp.metadata.businesstype.api.MdBizTypeMappingService;
import com.inspur.edp.wf.bizprocess.entity.FormButton;
import com.inspur.edp.wf.bizprocess.entity.FormField;
import com.inspur.edp.wf.bizprocess.entity.FormFieldData;
import com.inspur.edp.wf.bizprocess.entity.FormFormat;
import com.inspur.edp.wf.bizprocess.entity.FormMethod;
import com.inspur.edp.wf.bizprocess.entity.MethodParameter;
import com.inspur.edp.wf.bizprocess.entity.UrlParameter;
import com.inspur.edp.wf.bizprocess.service.FormFormatRpcService;
import com.inspur.lcm.metadata.logging.LoggerDisruptorQueue;
import io.iec.edp.caf.boot.context.CAFContext;
import io.iec.edp.caf.commons.exception.ExceptionLevel;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.rpc.client.RpcClassHolder;
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;


public class VoBuildFormFormatServiceImpl implements VoBuildFormFormatService {

    private final NoCodeService noCodeService = SpringBeanUtils.getBean(NoCodeService.class);

    private final MetadataService metadataService;

    private final MdBizTypeMappingService mdBizTypeMappingService;

    private static final Logger logger = LoggerFactory.getLogger(VoBuildFormFormatServiceImpl.class);

    public VoBuildFormFormatServiceImpl(MetadataService metadataService, MdBizTypeMappingService mdBizTypeMappingService) {
        this.metadataService = metadataService;
        this.mdBizTypeMappingService = mdBizTypeMappingService;
    }

    /**
     * 设计时在表单保存以及推送到工作流时接受表单传入的界面规则转换为流程格式推送至流程设计
     * 包括字段、按钮以及事件的格式转换
     *
     * @param voFormModel 界面规则
     * @param voId        表单对应的VOID
     * @param bizCategory 业务种类ID
     */
    @Override
    public void buildFormFormat(VoFormModel voFormModel, String voId, String bizCategory) {
        if (voFormModel == null) {
            throw new ViewModelException(FormFormatErrorCodes.GSP_VIEWOBJECT_FORMFORMAT_1001, null, ExceptionLevel.Error, false);
        }
        FormFormat format = new FormFormat();
        String formId = voFormModel.getId();
        format.setId(formId);
        format.setCode(voFormModel.getCode());
        List<String> bizTypeIds = mdBizTypeMappingService.getBizTypeIdsByBoId(bizCategory);
        if(bizTypeIds == null || bizTypeIds.isEmpty()){
            LoggerDisruptorQueue.publishEvent(
                    VMI8nResourceUtil.getMessage(VoResourceKeyNames.ERROR_PUBLISH_FORM_WITHOUT_BO_TO_PROCESS_DESIGN),
                    CAFContext.current.getUserId()
            );
            logger.warn("bizTypeId is null");
            return;
        }
        String bizTypeId = bizTypeIds.get(0);
        format.setBizCategory(bizTypeId);
        format.setName(voFormModel.getName());
        format.setFormatType(voFormModel.getFormatType());
        if (voFormModel.getUrl() == null || voFormModel.getUrl().isEmpty()) {
            throw new ViewModelException(FormFormatErrorCodes.GSP_VIEWOBJECT_FORMFORMAT_1002, null, ExceptionLevel.Error, false);
        }
        format.setFormUrl(voFormModel.getUrl());
        if (voFormModel.getUrlParameters() == null || voFormModel.getUrlParameters().isEmpty()) {
            throw new ViewModelException(FormFormatErrorCodes.GSP_VIEWOBJECT_FORMFORMAT_1003, null, ExceptionLevel.Error, false);
        }
        format.setUrlParameters(convertUrlParameters(voFormModel.getUrlParameters()));
        if (voFormModel.getUrlType() == null || voFormModel.getUrlType().isEmpty()) {
            throw new ViewModelException(FormFormatErrorCodes.GSP_VIEWOBJECT_FORMFORMAT_1004, null, ExceptionLevel.Error, false);
        }
        format.setUrlType(voFormModel.getUrlType());
        format.setTerminal(voFormModel.getTerminal());
        //传入的按钮组信息
        List<ButtonGroup> buttons = voFormModel.getButtonGroup();
        if (buttons != null && !buttons.isEmpty()) {
            List<FormButton> formButtons = buildButton(buttons, formId);
            format.setFormButtons(formButtons);
        }
        //字段部分的信息结构
        ObjectData objectData = voFormModel.getObjectAuthInfo();
        if (objectData != null) {
            FormFieldData formFieldData = buildFieldData(objectData, formId);
            List<FormFieldData> formFieldDatas = new ArrayList<>();
            formFieldDatas.add(formFieldData);
            format.setFormFields(formFieldDatas);
        }
        //事件信息结构转换
        List<MethodGroup> methodGroups = voFormModel.getMethodGroup();
        if (methodGroups != null && !methodGroups.isEmpty()) {
            List<FormMethod> formMethods = buildMethod(methodGroups, formId);
            format.setFormMethods(formMethods);
        }
        //检测已建表单VO内是否存在对应流程自定义变量
        checkFormConfigID(voId, voFormModel.getProjectPath());
        //调用流程rpc服务推送
        RpcClassHolder rpcClassHolder = SpringBeanUtils.getBean(RpcClassHolder.class);
        FormFormatRpcService formFormatRpcService = rpcClassHolder.getRpcClass(FormFormatRpcService.class);
        formFormatRpcService.addFormFormat(format);
    }

    /**
     * 将前端表单传递获取的button信息转化为表单格式内的button信息格式
     *
     * @param buttonGroups 前端表单传递的按钮组信息
     * @param formatID     表单格式ID
     * @return List<FormButton>
     */
    private List<FormButton> buildButton(List<ButtonGroup> buttonGroups, String formatID) {
        List<FormButton> formButtons = new ArrayList<>();
        buttonGroups.forEach(buttonGroup -> {
            if (buttonGroup.getButtons() == null) {
                // 跳过当前按钮组，如果其按钮列表为null,正常情况下应不会存在传入按钮组为null的情况
                return;
            }
            String buttonGroupName = buttonGroup.getName();
            List<Button> buttons = buttonGroup.getButtons();
            buttons.forEach(button -> processButton(formButtons, button, formatID, buttonGroupName));
        });
        return formButtons;
    }

    /**
     * 将表单格式传入的按钮信息转化为流程格式所需并添加到List<FormButton>内，button转换为formButton
     *
     * @param formButtons     推送至流程格式创立的formButton数组
     * @param button          表单传入的单个按钮信息
     * @param formatID        表单格式ID
     * @param buttonGroupName 按钮组名称 对单个按钮的名称信息进行拼接：按钮组名称/按钮名称
     */
    private void processButton(List<FormButton> formButtons, Button button, String formatID, String buttonGroupName) {
        FormButton formButton = createFormButton(button, formatID);
        formButton.setButtonName(buttonGroupName + "/" + button.getName());
        formButtons.add(formButton);
        if (button.getButtons() != null) {
            // 顺序处理子按钮
            button.getButtons().forEach(childButton -> processChildButton(formButtons, childButton, formatID, buttonGroupName, button.getName()));
        }
    }

    /**
     * 处理按钮下存在的子级按钮，同样需进行名称拼接
     *
     * @param formButtons     推送至流程格式创立的formButton数组
     * @param childButton     表单传入的单个按钮信息
     * @param formatID        表单格式ID
     * @param buttonGroupName 按钮组名称 对单个按钮的名称信息进行拼接：按钮组名称/父级按钮/按钮名称
     */
    private void processChildButton(List<FormButton> formButtons, Button childButton, String formatID, String buttonGroupName, String parentButtonName) {
        FormButton childButton1 = createFormButton(childButton, formatID);
        childButton1.setButtonName(buttonGroupName + "/" + parentButtonName + "/" + childButton.getName());
        formButtons.add(childButton1);
    }

    /**
     * 根据表单传入的button信息以及表单格式ID创建formButton
     *
     * @param button   表单传入的按钮信息
     * @param formatID 表单格式ID
     * @return formButton 流程按钮格式
     */
    private FormButton createFormButton(Button button, String formatID) {
        FormButton formButton = new FormButton();
        formButton.setId(UUID.randomUUID().toString());
        formButton.setButtonId(button.getCode());
        formButton.setFormFormatId(formatID);
        formButton.setConfigurableStates(mappingButtonConfigure(button.getConfigurableAttrs()));
        return formButton;
    }

    /**
     * 将前端表单传递获取的字段信息转化为表单格式内的字段信息格式
     *
     * @param objectData   表单传入的节点信息，存在主子表分级结构
     * @param formFormatId 表单格式ID
     * @return FormFieldData
     */
    private FormFieldData buildFieldData(ObjectData objectData, String formFormatId) {
        FormFieldData formFieldData = createTableData(objectData);
        //建立表单格式上对应的children字段存放表单传递过来的字段信息
        List<FormFieldData> children = new ArrayList<>();
        if (objectData.getElements() != null) {
            //当前表节点下包含的字段信息
            List<ChildData> elements = objectData.getElements();
            //主表字段信息转换,得到加入主表字段信息的children
            elements.forEach(element -> addElementData(element, children, formFormatId, null));
        }
        //获取子表数组信息
        List<ObjectData> childObjects = objectData.getChildObjects();
        if (childObjects != null && !childObjects.isEmpty()) {
            childObjects.forEach(childObject -> {
                //子表字段信息转换
                addChildData(childObject, children, formFormatId);
            });
        }
        formFieldData.setChildren(children);
        return formFieldData;
    }

    /**
     * 将表单传入的子表信息进行格式转换，使用addElementData进行字段信息的转化，存在名称的拼接，字段前以#分割节点名称
     *
     * @param objectData   节点信息
     * @param children     转换后的流程字段数组
     * @param formFormatId 表单格式ID
     */
    private void addChildData(ObjectData objectData, List<FormFieldData> children, String formFormatId) {
        FormFieldData formFieldData = createTableData(objectData);
        List<FormFieldData> childElement = new ArrayList<>();
        //当前表节点下包含的字段信息
        List<ChildData> elements = objectData.getElements();
        String nodeCode = objectData.getCode();
        elements.forEach(element -> {
            //增加子表节点标识，以#分割
            element.setCode(nodeCode + "#" + element.getCode());
            addElementData(element, childElement, formFormatId, nodeCode);
        });
        //子表下若还有子表
        List<ObjectData> childObjects = objectData.getChildObjects();
        if (childObjects != null && !childObjects.isEmpty()) {
            childObjects.forEach(childObject -> {
                childObject.setCode(nodeCode + "#" + childObject.getCode());
                //递归处理子表信息
                addChildData(childObject, childElement, formFormatId);
            });
        }
        formFieldData.setChildren(childElement);
        children.add(formFieldData);
    }

    /**
     * 读取传入的表单字段信息转为流程字段信息进行存储
     *
     * @param element      表单字段信息
     * @param children     转换后的流程字段数组
     * @param formFormatId 表单格式ID
     * @param nodeCode     节点标识
     */

    //第二步得到的放入了关联内 又放入了子表内
    private void addElementData(ChildData element, List<FormFieldData> children, String formFormatId, String nodeCode) {
        //每一个FormFieldData对应了当前表节点的一条字段信息
        FormFieldData data = createData(element, formFormatId);
        //UDT字段及关联字段处理
        if (element.getChildFields() != null && !element.getChildFields().isEmpty()) {
            element.getChildFields().forEach(childField -> {
                FormFieldData associateData = createData(childField, formFormatId);
                //主表无需添加节点标识，只需要子表添加
                setNodeCode(associateData, childField, nodeCode);
                if (childField.getChildFields() != null && !childField.getChildFields().isEmpty()) {
                    //二层以上的嵌套，例如关联内含有UDT时特殊处理下，否则无表节点标识
                    childField.setCode(associateData.getData().getFieldId());
                    addElementData(childField, data.getChildren(), formFormatId, nodeCode);
                } else {
                    data.getChildren().add(associateData);
                }
            });
        }
        children.add(data);
    }

    /**
     * 创建字段的data信息
     *
     * @param fieldData    表单传入的单个字段信息
     * @param formFormatId 表单格式ID
     * @return FormFieldData 流程格式的字段信息
     */
    //创建字段的data信息
    private FormFieldData createData(ChildData fieldData, String formFormatId) {
        FormFieldData data = new FormFieldData();
        FormField formField = new FormField();
        formField.setId(UUID.randomUUID().toString());
        formField.setFieldState(fieldData.getState());
        formField.setFieldId(fieldData.getCode());
        formField.setFieldName(fieldData.getName());
        formField.setConfigurableStates(mappingFieldConfigure(fieldData.getConfigurableAttrs()));
        formField.setFormFormatId(formFormatId);
        data.setData(formField);
        data.setExpanded(true);
        data.setLeaf(true);
        return data;
    }

    /**
     * 创建表节点的data信息
     *
     * @param objectData 表单传入的表节点信息
     * @return FormFieldData 流程格式的表节点信息，与字段信息相比无formFormatId
     */
    private FormFieldData createTableData(ObjectData objectData) {
        FormFieldData formFieldData = new FormFieldData();
        FormField data = new FormField();
        data.setId(UUID.randomUUID().toString());
        data.setFieldName(objectData.getName());
        data.setFieldId(objectData.getCode());
        data.setConfigurableStates(new ArrayList<>());
        formFieldData.setData(data);
        formFieldData.setExpanded(true);
        formFieldData.setLeaf(true);
        return formFieldData;
    }

    /**
     * 判断设置节点标识，以#分割，只对子表进行设置
     *
     * @param formFieldData 流程字段
     * @param data          表单字段
     * @param nodeCode      节点信息 子表传入，主表传入为空
     */
    private void setNodeCode(FormFieldData formFieldData, ChildData data, String nodeCode) {
        if (nodeCode != null) {
            formFieldData.getData().setFieldId(nodeCode + "#" + data.getCode());
        } else {
            formFieldData.getData().setFieldId(data.getCode());
        }
    }

    /**
     * 将前端表单传递获取的事件信息转化为表单格式内的事件信息格式
     *
     * @param methods  前端表单传递的事件信息
     * @param formatID 表单格式ID
     * @return List<FormMethod>
     */
    private List<FormMethod> buildMethod(List<MethodGroup> methods, String formatID) {
        List<FormMethod> formMethods = new ArrayList<>();
        methods.forEach(method -> {
            FormMethod formMethod = new FormMethod();
            formMethod.setId(UUID.randomUUID().toString());
            formMethod.setMethod(method.getMethodCode());
            formMethod.setMethodName(method.getMethodName());
            formMethod.setFormFormatId(formatID);
            List<MethodParameter> formMethodParam = getMethodParameters(method);
            if (!formMethodParam.isEmpty()) {
                formMethod.setMethodParameters(formMethodParam);
            }
            formMethods.add(formMethod);
        });
        return formMethods;
    }

    /**
     * 将表单传入的单个事件信息内所包含的事件参数转化为流程格式所需
     *
     * @param methodGroup 表单传入的单个事件信息
     * @return List<MethodParameter> 是流程格式所需要的事件参数信息
     */
    private List<MethodParameter> getMethodParameters(MethodGroup methodGroup) {
        List<MethodParameter> formMethodParam = new ArrayList<>();
        List<MethodParam> methodParam = methodGroup.getMethodParam();
        if (methodParam != null && !methodParam.isEmpty()) {
            methodParam.forEach(value -> {
                MethodParameter param = new MethodParameter();
                param.setCode(value.getCode());
                param.setName(value.getName());
                formMethodParam.add(param);
            });
        }
        return formMethodParam;
    }

    /**
     * 判断VO表单流程配置变量是否存在，不存在则增加
     *
     * @param viewModelID 表单对应VO元数据ID
     * @param projectPath 工程路径
     */
    private void checkFormConfigID(String viewModelID, String projectPath) {
        GspMetadata metadata;
        //获取表单对应的VO
        if (projectPath.isEmpty()) {
            metadata = this.noCodeService.getMetadata(viewModelID);
        } else {
            metadata = this.metadataService.loadMetadataByMetadataId(viewModelID, projectPath);
        }
        if (metadata == null || metadata.getContent() == null) {
            throw new ViewModelException(FormFormatErrorCodes.GSP_VIEWOBJECT_FORMFORMAT_1005, null, ExceptionLevel.Error, false);
        }
        GspViewModel vo = (GspViewModel) metadata.getContent();
        @NonNull
        CommonVariableCollection variables = vo.getVariables().getContainElements();
        if (variables == null || variables.isEmpty() || !isExistFormConfigID(variables)) {
            setFormConfigID(vo);
        }
        metadata.setContent(vo);
        try {
            if (projectPath.isEmpty()) {
                this.noCodeService.saveMetadata(metadata);
            } else {
                this.metadataService.saveMetadata(metadata, metadata.getRelativePath() + "/" + metadata.getHeader().getFileName());
            }
        } catch (RuntimeException e) {
            throw new ViewModelException(FormFormatErrorCodes.GSP_VIEWOBJECT_FORMFORMAT_1006, null, ExceptionLevel.Error, false);
        }
    }

    /**
     * 定义表单流程配置变量并添加到对应VO
     *
     * @param vo 表单对应的VO元数据
     */
    private void setFormConfigID(GspViewModel vo) {
        CommonVariable variable = new CommonVariable();
        variable.setID(UUID.randomUUID().toString());
        variable.setCode("bffSysFormConfigId");
        variable.setLabelID("bffSysFormConfigId");
        variable.setName(VMI8nResourceUtil.getMessage(VoResourceKeyNames.FROM_PROCESS_CONFIG));
        variable.setMDataType(GspElementDataType.forValue(0));
        variable.setLength(36);
        vo.getVariables().getContainElements().addField(variable);
    }

    /**
     * VO上原来是否存在表单流程配置变量
     *
     * @param variableCollection VO变量集合
     * @return 布尔值 存在流程变量返回true，不存在返回false
     */
    private boolean isExistFormConfigID(CommonVariableCollection variableCollection) {
        for (IGspCommonField variable : variableCollection) {
            if (("bffSysFormConfigId").equals(variable.getLabelID())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 将表单传入的Url参数集转换为流程格式需要的UrlParameter
     *
     * @param parameters 表单传入的Url参数集
     * @return List<UrlParameter>
     */
    private List<UrlParameter> convertUrlParameters(List<Parameters> parameters) {
        List<UrlParameter> urlParameters = new ArrayList<>();
        if (parameters == null || parameters.isEmpty()) {
            throw new ViewModelException(FormFormatErrorCodes.GSP_VIEWOBJECT_FORMFORMAT_1003, null, ExceptionLevel.Error, false);
        }
        parameters.forEach(parameter -> {
            UrlParameter urlParameter = new UrlParameter();
            urlParameter.setCode(parameter.getCode());
            urlParameter.setName(parameter.getName());
            urlParameter.setValueType(parameter.getValueType());
            urlParameter.setValue(parameter.getValue());
            urlParameters.add(urlParameter);
        });
        return urlParameters;
    }

    /**
     * 将表单传入的按钮配置转换为流程格式需要的按钮配置
     *
     * @param buttonConfigure 表单传入的按钮权限配置 将Disable变为流程对应的Disabled以及Enable属性,Visible转为Hidden
     * @return List<String>
     */
    private List<String> mappingButtonConfigure(List<String> buttonConfigure) {
        List<String> buttonState = new ArrayList<>();
        if (buttonConfigure == null || buttonConfigure.isEmpty()) {
            return buttonState;
        }
        for (String field : buttonConfigure) {
            if (("Disable").equals(field)) {
                // 当遇到"Disable"时，映射为"Disabled"和"Enable"
                buttonState.add("Disabled");
                buttonState.add("Enable");
            } else if ("Visible".equals(field)) {
                buttonState.add("Hidden");
            }
        }
        return buttonState;
    }

    /**
     * 将表单传入的元素配置转换为流程格式需要的元素配置
     *
     * @param fieldConfigure 表单传入的字段权限配置 将Readonly变为流程对应的Editable以及Readonly属性，Visible转为Hidden，
     * @return List<String>
     */
    private List<String> mappingFieldConfigure(List<String> fieldConfigure) {
        List<String> fieldState = new ArrayList<>();
        if (fieldConfigure == null || fieldConfigure.isEmpty()) {
            return fieldState;
        }
        for (String field : fieldConfigure) {
            if ("Readonly".equals(field)) {
                // 当遇到"Readonly"时，映射为"Readonly"和"Editable"
                fieldState.add("Readonly");
                fieldState.add("Editable");
            } else if ("Visible".equals(field)) {
                fieldState.add("Hidden");
            } else {
                fieldState.add(field);
            }
        }
        return fieldState;
    }
}

